package com.lock.redisLock;

import java.time.Duration;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import com.lock.RLock;
import com.lock.util.LockUtilsConfig;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;

import io.lettuce.core.SetArgs;
import io.lettuce.core.cluster.api.sync.RedisClusterCommands;

/**
 * 基于Lettuce实现的redis分布式锁
 *
 * @author 007
 * Jedis、Redisson、Lettuce的主要区别：
 * <p>
 * Jedis：使用阻塞的I/O，且其方法调用都是同步的，程序流需要等到sockets处理完I/O才能执行，不支持异步。
 * Jedis客户端实例不是线程安全的，所以需要通过连接池来使用Jedis。
 * 比较全面的提供了Redis的操作特性
 * <p>
 * Redisson：基于Netty框架的事件驱动的通信层，其方法调用是异步的。
 * Redisson的API是线程安全的，所以可以操作单个Redisson连接来完成各种操作。
 * 促使使用者对Redis的关注分离，提供很多分布式相关操作服务，例如，分布式锁，分布式集合，可通过Redis支持延迟队列。
 * <p>
 * Lettuce：基于Netty框架的事件驱动的通信层，其方法调用是异步的。
 * Lettuce的API是线程安全的，所以可以操作单个Lettuce连接来完成各种操作。
 * 主要在一些分布式缓存框架上使用比较多。
 */
public class RedisLock implements RLock {
    private static final Logger log = LoggerFactory.getLogger(RedisLock.class);
    private static final String LOCKNAME_PREFIX = "rlock_";
    private static final String OK = "OK";
    /**
     * 重试时间间隔
     */
    private static final int INTERVAL = 20;

    // private static final String setMode_NX = "NX";// Only set the key if it does not already exist.
    // private static final String setMode_XX = "XX";// Only set the key if it already exist.
    // private static final String expTimeunits_EX = "EX";// EX = seconds, expire time units: EX = seconds;
    // private static final String expTimeunits_PX = "PX";// PX = milliseconds, expire time units: PX = milliseconds
    // public static final long LOCK_EXPIRATION_INTERVAL_SECONDS = 60;

    // protected long internalLockLeaseTime = TimeUnit.SECONDS.toMillis(LOCK_EXPIRATION_INTERVAL_SECONDS);

    // 锁状态标志
    private boolean locked = false;
    private final String lockKey;
    private final String uuidValue;
    private RedisClusterCommands<String, String> lettuce = null;

    public RedisLock(RedisClusterCommands<String, String> jedis, String name) {
        this.lockKey = LOCKNAME_PREFIX + LockUtilsConfig.GOLBAL_LOCK_NAME_PREFIX + name;
        this.lettuce = jedis;
        this.uuidValue = UUID.randomUUID().toString() + "-" + Thread.currentThread().getId();
    }

    @Override
    public void close() {
        unlock();
    }

    @Override
    public void lock() throws Exception {
        locked = locked ? locked : (this.tryLockInner(-1L, null, LockUtilsConfig.RLOCK_AUTO_TIMEOUT_MS, TimeUnit.MILLISECONDS) == null);
    }

    @Override
    public boolean tryLock() throws Exception {
        return tryLock(0L, TimeUnit.MILLISECONDS);
    }

    @Override
    public boolean tryLock(Duration waitTime) throws Exception {
        return tryLock(waitTime.toMillis(), TimeUnit.MILLISECONDS);
    }

    @Override
    public boolean tryLock(long waitTime, TimeUnit waitUnit) throws Exception {
        locked = locked ? locked : (this.tryLockInner(waitTime, waitUnit, LockUtilsConfig.RLOCK_AUTO_TIMEOUT_MS, TimeUnit.MILLISECONDS) == null);
        return locked;
    }

    @Override
    public boolean tryLock(Duration waitTime, Duration leaseTime) throws Exception {
        return tryLock(waitTime.toMillis(), TimeUnit.MILLISECONDS, leaseTime.toMillis(), TimeUnit.MILLISECONDS);
    }

    @Override
    public boolean tryLock(long waitTime, TimeUnit waitUnit, long leaseTime, TimeUnit leaseUnit) throws Exception {
        locked = locked ? locked : (this.tryLockInner(waitTime, waitUnit, leaseTime, leaseUnit) == null);
        return locked;
    }

    /**
     * @param waitTime
     * @param waitUnit
     * @param leaseTime
     * @param leaseUnit
     * @return null:获取锁成功,
     * 正整数:其他人获取锁的生命时长,
     * -2:key不存在但是创建锁失败,
     * -1:有key但无生命周期值
     * @throws Exception
     */
    private Long tryLockInner(final long waitTime, final TimeUnit waitUnit, final long leaseTime, final TimeUnit leaseUnit) throws Exception {
        Preconditions.checkNotNull(lettuce != null, "lettuce is null");

        long lockLeaseTime = leaseUnit.toMillis(leaseTime);
        boolean doDelete = false;

        long startNano = System.nanoTime();
        long endNano = 0;
        // 如果小于零, 等于永远等待
        if (waitTime < 0 || waitUnit == null) {
            endNano = Long.MAX_VALUE;
        } else {
            endNano = startNano + (waitUnit.toMillis(waitTime) * 1_000_000L);
        }
        long currNano = 0;
        try {
            do {
                String rtnCode = this.lettuce.set(lockKey, uuidValue, SetArgs.Builder.nx().px(lockLeaseTime));
                // log.debug("SET_NX_PX, key={}, uuidValue={},lockLeaseTime={}, rtnCode={}",
                // lockKey, uuidValue, lockLeaseTime, rtnCode); // SETNX成功，则成功获取一个锁
                if (StringUtils.equals(rtnCode, OK)) {
                    // SETNX失败，说明锁已存在,检查是否被自己保持,还是被其他对象保持，检查其是否已经超时
                    // log.trace("OK:{}:{}, ttl:{}", lockKey, lettuce.get(lockKey), lettuce.pttl(lockKey));
                    return null;
                } else {
                    // log.trace("NA:{}:{}, ttl:{}, tryTime:{},maxRetryTimes:{}", lockKey, lettuce.get(lockKey), lettuce.pttl(lockKey), tryTime, maxRetryTimes);
                    // 当 key 不存在时，返回 -2 。
                    // 当 key 存在但没有设置剩余生存时间时，返回 -1 。
                    // 否则，以毫秒为单位，返回 key 的剩余生存时间。
                    TimeUnit.MILLISECONDS.sleep(INTERVAL);
                }
                currNano = System.nanoTime();
            } while (endNano >= currNano);
            // log.trace("超时退出:{}:{}, ttl:{}", lockKey, lettuce.get(lockKey), lettuce.pttl(lockKey));
            return new Long(lettuce.pttl(lockKey));
        } catch (Exception e) {
            doDelete = true;
            throw e;
        } finally {
            if (doDelete) {
                unlock();
            }
        }
    }

    @Override
    public void unlock() {
        if (locked) {
            lettuce.del(lockKey);
        }
        locked = false;
    }

    @Override
    public boolean isLocked() {
        return lettuce.exists(lockKey) == 1;
    }

    @Override
    public boolean isHoldByCurrentThread() {
        return StringUtils.equals(lettuce.get(lockKey), uuidValue);
    }

    @Override
    public int getHoldCount() {
        return 1;
    }

    /**
     * 计算充实
     *
     * @param time
     * @param timeUnit
     * @return
     * @throws RuntimeException
     */
    private static long countWaitRetryTimes(long time, TimeUnit timeUnit) {
        if (time < 0 || timeUnit == null) {// 如果小于零, 等于永远等待
            return Long.MAX_VALUE;
        }

        long ms = timeUnit.toMillis(time);
        long mod = ms % INTERVAL;
        long waitRetryTimes = 0;
        if (mod == 0) {
            waitRetryTimes = ms / INTERVAL;
        }
        waitRetryTimes = ms / INTERVAL + 1;
        return waitRetryTimes;
    }

}
